/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.cuni.amis.pogamut.episodic.lispbots;

import cz.cuni.amis.pogamut.episodic.memory.*;
import cz.cuni.amis.pogamut.episodic.episodes.*;
import cz.cuni.amis.pogamut.episodic.visualizer.VisualizationCreator;
import cz.cuni.amis.pogamut.episodic.decisions.Node;
import cz.cuni.amis.pogamut.episodic.decisions.NodeType;
import cz.cuni.amis.pogamut.episodic.memory.Parameters;
import cz.cuni.amis.pogamut.episodic.schemas.*;
import java.io.*;
import java.util.*;
import org.apache.commons.cli.*;

/**
 *
 * @author Zdenek Behan
 */
public class ExperimentRunner {
    // For looking at parsed args.
    static CliArgs parameters;
    AgentMemory mem;

    public ExperimentRunner(CliArgs _parameters) {
        parameters = _parameters;
    }

    private AgentMemory readMemory(String fileName) {
        try {
            FileInputStream in = new FileInputStream(fileName);
            ObjectInputStream objin = new ObjectInputStream(in);

            parameters.time = objin.readInt();
            AgentMemory mem = (AgentMemory)objin.readObject();

            mem.sem.release();

            return mem;
        } catch (IOException ex) {
            System.out.println("Exception in readMemory: " + ex.getMessage());
        } catch (ClassNotFoundException ex2) {
            System.out.println("Exception in readMemory: " + ex2.getMessage());
        }
        return null;
    }

    private void saveMemory(AgentMemory mem, String fileName) {
        try {
            mem.sem.acquireUninterruptibly();
            FileOutputStream f = new FileOutputStream(fileName);
            ObjectOutputStream s = new ObjectOutputStream(f);
            s.writeInt(parameters.time);
            s.writeObject(mem);
            s.flush();
        } catch (IOException ex) {
        } finally {
            mem.sem.release();
        }
    }

    public boolean Run() {
        // Initialize memory
        if (parameters.memin != "") {
            mem = readMemory(parameters.memin);
        } else {
            // Init a new & empty one.
            AgentMemory.visualizeLive = true;
            mem = new AgentMemory();
        }

        long startTimestamp = new java.util.Date().getTime();
        System.out.println("Starting experiment " + parameters.experiment + ":");
        boolean rval;
        switch(parameters.experiment) {
            case 1:   rval = experiment1RetentionCurve(true); break;
            case 2:   rval = experiment2RecallAccuracy(); break;
            case 3:   rval = experiment3TemporalSchemas(); break;
            case 4:   rval = experiment4TotalVolume(); break;
            case 998: rval = experiment998VeryLongData(); break;
            case 999: rval = experiment999Basic(); break;
            default: System.err.println("Invalid experiment number!"); return false;
        }
        if (rval) {
            System.out.println("Succeeded experiment " + parameters.experiment + "    " + (new java.util.Date().getTime() - startTimestamp) / 1000 + "secs taken.");
        } else {
            System.out.println("Failed experiment " + parameters.experiment + "    " + (new java.util.Date().getTime() - startTimestamp) / 1000 + "secs taken.");
        }

        if (parameters.memout != "") {
            // Save memory if desired.
            saveMemory(mem, parameters.memout);
        }

        if (parameters.visualise) {
            // Run the visualiser.
            VisualizationCreator viz = new VisualizationCreator();
            mem.addVisualizationListener(viz);
            mem.initVisualizers();
        }

        return rval;
    }

    public class DaysCounter {
        public int days;
        long weekTimestamp = new java.util.Date().getTime();
        long monthTimestamp = new java.util.Date().getTime();
        long seasonTimestamp = new java.util.Date().getTime();
        long yearTimestamp = new java.util.Date().getTime();
        public boolean verbose = false;

        public DaysCounter(boolean _verbose) {
            verbose = _verbose;
        }

        public void incDay() {
            if ((days % 7) == 0 ) {
                System.out.println("Week " + ((days / 7) % 4) + "    Milliseconds taken: " + ((new java.util.Date().getTime()) - weekTimestamp));
                weekTimestamp = new java.util.Date().getTime();
            }
            if ((days % 28 ) == 0) {
                System.out.println(" Month " + ((days / 28) % 12) + "    Milliseconds taken: " + ((new java.util.Date().getTime()) - monthTimestamp));
                monthTimestamp = new java.util.Date().getTime();
            }
            if ((days % 336 ) == 0) {
                System.out.println("   Year " + (days / 336) + "    Milliseconds taken: " + ((new java.util.Date().getTime()) - yearTimestamp));
                yearTimestamp = new java.util.Date().getTime();
            }
            days++;
        }
    }

// ********************* Retention curve *******************************
    public class RetentionPreHook implements MemoryEventHooks {
        AgentMemory mem;
        DaysCounter days = new DaysCounter(true);
        int timelimit;
        int totalEpisodes = 0;
        ArrayList<Integer> episodesIn = new ArrayList<Integer>();
        ArrayList<Integer> episodesRememberedTotal = new ArrayList<Integer>();
        ArrayList<Integer> episodesRemembered0 = new ArrayList<Integer>();
        ArrayList<Integer> episodesRemembered1 = new ArrayList<Integer>();
        ArrayList<Integer> episodesRemembered2 = new ArrayList<Integer>();
        ArrayList<Integer> episodesRemembered3 = new ArrayList<Integer>();
        ArrayList<Integer> episodeTotals = new ArrayList<Integer>();
        ArrayList<Chronobag> chronobagsIn = new ArrayList<Chronobag>();
        ArrayList<Chronobag> chronobagsIn1 = new ArrayList<Chronobag>();
        ArrayList<Chronobag> chronobagsIn2 = new ArrayList<Chronobag>();
        ArrayList<Chronobag> chronobagsIn3 = new ArrayList<Chronobag>();


        Chronobag oldestBag = null;
        ArrayList<Integer> oldestEpisodes = new ArrayList<Integer>();

        public RetentionPreHook(AgentMemory _mem, int _timelimit) {
            mem = _mem;
            timelimit = _timelimit;
        }

        public boolean startNode(String goal, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean closeNode(String goal, ArrayList<String> trace) { return true; }
        public boolean executeAction(String action, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean advanceTime(long _time) { return true; }
        public boolean endOfDay() {
            if (days.days == 0) {
                oldestBag = mem.chrono.present;
            }
            days.incDay();

            // Look at present chronobag to see original episodes. Present always exists.
            episodesIn.add(mem.chrono.present.getEpisodes().size());
            totalEpisodes += mem.chrono.present.getEpisodes().size();
            episodeTotals.add(totalEpisodes);

            Collection<Episode> chronology = mem.chrono.getEpisodeCollection();
            episodesRememberedTotal.add(chronology.size());
            Collection<Episode> chronologyLevel = new ArrayList<Episode>(chronology);
            episodesRemembered0.add(mem.chrono.episodeLevelSelector(chronologyLevel, 0).size());
            chronologyLevel = new ArrayList<Episode>(chronology);
            episodesRemembered1.add(mem.chrono.episodeLevelSelector(chronologyLevel, 1).size());
            chronologyLevel = new ArrayList<Episode>(chronology);
            episodesRemembered2.add(mem.chrono.episodeLevelSelector(chronologyLevel, 2).size());
            chronologyLevel = new ArrayList<Episode>(chronology);
            episodesRemembered3.add(mem.chrono.episodeLevelSelector(chronologyLevel, 3).size());

            // Also preserve the chronobags at all levels in the proper order. Otherwise they might
            // be deleted. We want to look at the contents later.
            if (mem.chrono.present.older != null) {
                chronobagsIn.add(mem.chrono.present);
                chronobagsIn1.add(mem.chrono.present.older.parent);
                chronobagsIn2.add(mem.chrono.present.older.parent.parent);
                chronobagsIn3.add(mem.chrono.present.older.parent.parent.parent);
            }

            if (oldestBag != null) {
                // Make sure it still exists.
                oldestEpisodes.add(oldestBag.getEpisodes().size());
            }

            if (days.days > timelimit) {
                return false;
            }

            return true;
        }
    }

    public class RetentionPostHook implements MemoryEventHooks {
        AgentMemory mem;

        public RetentionPostHook (AgentMemory _mem) {
            mem = _mem;
        }

        public boolean startNode(String goal, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean closeNode(String goal, ArrayList<String> trace) { return true; }
        public boolean executeAction(String action, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean advanceTime(long _time) { return true; }
        public boolean endOfDay() { return true; }
    }

    boolean experiment1RetentionCurve(boolean episodeMerging) {
        System.out.println("* Mundane retention curve (using repetitive episodes). Episode merging: " + episodeMerging);
        System.out.println("* Looks at the rough volume of remembered episodes.");
        if (parameters.eventtree != "") {
            System.err.println("Info: Using " + parameters.eventtree + " as input.");
            if (parameters.descriptor == "") {
                System.err.println("Error: descriptor for this domain does not exist or none specified.");
            }

            // Set static parameters here.
            Parameters.CHRONOBAG_INTERVAL_LENGHTS[0] = 1;
            Parameters.CHRONOBAG_INTERVAL_LENGHTS[1] = 7;
            Parameters.CHRONOBAG_INTERVAL_LENGHTS[2] = 28;
            Parameters.CHRONOBAG_INTERVAL_LENGHTS[3] = 336;
            Parameters.LANDMARK_AGE_THRESHOLDS[0] = 60;
            Parameters.LANDMARK_AGE_THRESHOLDS[1] = 700;
            Parameters.LANDMARK_AGE_THRESHOLDS[2] = 2800;
            Parameters.LANDMARK_AGE_THRESHOLDS[3] = 4000;
            Parameters.FORGETTING_CURVE_EXPONENT[0] = -1.7;
            Parameters.FORGETTING_CURVE_EXPONENT[1] = -1.0;
            Parameters.FORGETTING_CURVE_EXPONENT[2] = -0.5;
            Parameters.FORGETTING_CURVE_EXPONENT[3] = -0.2;


            // Set mem parameters here.
            mem = new AgentMemory();
            mem.parameters.FORGETTING_CURVE_COEFFICIENT = 1.1;
            mem.parameters.MAX_CHRONOBAG_NODES = 400;

            mem.parameters.NO_EPISODE_MERGING = !episodeMerging;
            mem.parameters.REMEMBER_SEEN_ITEMS = true;
            mem.parameters.MAX_OBJECT_SCORE = 50;
            mem.parameters.DEFAULT_OBJECT_SCORE = 30;
            mem.parameters.MIN_OBJECT_SCORE = 5;
            mem.parameters.PERCENTAGE_TO_DERIVE_OBJECT = 0.6;

            System.out.println("Time limit:" + parameters.timeLimit);
            System.out.println("* Power function: " + mem.parameters.FORGETTING_CURVE_COEFFICIENT + " * x ^ " + "[" +
            Parameters.FORGETTING_CURVE_EXPONENT[0] + "," +
            Parameters.FORGETTING_CURVE_EXPONENT[1] + "," +
            Parameters.FORGETTING_CURVE_EXPONENT[2] + "," +
            Parameters.FORGETTING_CURVE_EXPONENT[3] + "]");

            // Initialize the lisp tree
            LispTree parser;
            parser = new LispTree(parameters.eventtree);

            RetentionPreHook preHook = new RetentionPreHook(mem, parameters.timeLimit);
            RetentionPostHook postHook = new RetentionPostHook(mem);

            // Initialize memory feeder
            MemoryFeeder feeder = new MemoryFeeder(mem, parser, parameters.descriptor, preHook, postHook);

            // Feed tree into memory
            try {
                parser.walkInForest(new TraceToEventVisitor(null, null, null, feeder));
            } catch (Exception e) {
                System.err.println("Error: " + e.getMessage());
            }

            // Evaluation

            System.out.println("Experiment Results...");
            // Oldest node progression
            System.out.println("Oldest chronobag size progression:");
            System.out.println("Day; Episodes");
            int i = 1;
            Iterator<Integer> it = preHook.oldestEpisodes.iterator();
            while (it.hasNext()) {
                System.out.println(i + ";" + it.next() + ";");
                i++;
            }

            System.out.println("Output bag sizes:");
            System.out.println("NOTE: High-level bags are printed multiple times for proper tabelation, but they're only present once");
            System.out.println("Day; Age; Created episodes; Episodes remaining at level 0; 1; 2; 3;");
            i = 1;
            it = preHook.episodesIn.iterator();
            Iterator<Chronobag> it3 = preHook.chronobagsIn.iterator();
            Iterator<Chronobag> it4 = preHook.chronobagsIn1.iterator();
            Iterator<Chronobag> it5 = preHook.chronobagsIn2.iterator();
            Iterator<Chronobag> it6 = preHook.chronobagsIn3.iterator();
            while (it.hasNext() && it3.hasNext() && it4.hasNext() && it5.hasNext() && it6.hasNext()) {
                System.out.println(i + "; " + (parameters.timeLimit - i + 1) + "; " + it.next() + "; " +
                        it3.next().getEpisodes().size() + "; " +
                        it4.next().getEpisodes().size() + "; " + //" / " + Parameters.CHRONOBAG_INTERVAL_LENGHTS[1] + "; " +
                        it5.next().getEpisodes().size() + "; " + //" / " + Parameters.CHRONOBAG_INTERVAL_LENGHTS[2] + "; " +
                        it6.next().getEpisodes().size() + "; "); //" / " + Parameters.CHRONOBAG_INTERVAL_LENGHTS[3] + "; ");
                i++;
            }

            System.out.println("Chronobags by size:");
            for (i=0; i< Parameters.MAX_CHRONOBAG_LEVELS; i++) {
                System.out.println(i + ";" + mem.chrono.getChronobagsChronology(i).size() + ";");
            }
            System.out.println("Episodes happened: " + preHook.totalEpisodes);
            System.out.println("Episodes remembered by level:");
            i=1;
            Iterator<Integer> it7 = preHook.episodesRemembered0.iterator();
            Iterator<Integer> it8 = preHook.episodesRemembered1.iterator();
            Iterator<Integer> it9 = preHook.episodesRemembered2.iterator();
            Iterator<Integer> it10 = preHook.episodesRemembered3.iterator();
            Iterator<Integer> it11 = preHook.episodeTotals.iterator();
            Iterator<Integer> it12 = preHook.episodesIn.iterator();
            Iterator<Integer> it13 = preHook.episodesRememberedTotal.iterator();
            System.out.println("Day; Age; Episodes created; Episodes total; Episodes remembered at level 0; 1; 2; 3; Total; Compression Ratio;");
            while (it7.hasNext()) {
                int happened = it11.next();
                int remembered = it13.next();
                System.out.println(i + "; " + (parameters.timeLimit - i + 1) + "; "+ it12.next() + "; " + happened + "; " + it7.next() + "; " + it8.next() + "; " + it9.next() + "; " + it10.next() + "; " + remembered + "; " + ((double)(happened)/(double)(remembered)) );
                i++;
            }

            return true;
        }
        return false;
    }
// ********************* Retention curve *******************************
    public class RecallAccuracyPreHook implements MemoryEventHooks {
        AgentMemory mem;
        DaysCounter days = new DaysCounter(true);

        public RecallAccuracyPreHook(AgentMemory _mem) {
            mem = _mem;
        }

        public boolean startNode(String goal, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean closeNode(String goal, ArrayList<String> trace) { return true; }
        public boolean executeAction(String action, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean advanceTime(long _time) { return true; }
        public boolean endOfDay() {
            days.incDay();

            return true;
        }
    }

    public class RecallAccuracyPostHook implements MemoryEventHooks {
        AgentMemory mem;

        public RecallAccuracyPostHook (AgentMemory _mem) {
            mem = _mem;
        }

        public boolean startNode(String goal, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean closeNode(String goal, ArrayList<String> trace) { return true; }
        public boolean executeAction(String action, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean advanceTime(long _time) { return true; }
        public boolean endOfDay() { return true; }
    }

    boolean experiment2RecallAccuracy() {
        System.out.println("* Recall accuracy examines completeness and accuracy of the recalled episodes,");
        System.out.println("* as a function of number of cues given.");
        if (parameters.eventtree != "") {
            System.err.println("Info: Using " + parameters.eventtree + " as input.");
            if (parameters.descriptor == "") {
                System.err.println("Error: descriptor for this domain does not exist or none specified.");
            }
            // Set static parameters here.
            Parameters.CHRONOBAG_INTERVAL_LENGHTS[0] = 1;
            Parameters.CHRONOBAG_INTERVAL_LENGHTS[1] = 7;
            Parameters.CHRONOBAG_INTERVAL_LENGHTS[2] = 28;
            Parameters.CHRONOBAG_INTERVAL_LENGHTS[3] = 336;
            Parameters.LANDMARK_AGE_THRESHOLDS[0] = 300;
            Parameters.LANDMARK_AGE_THRESHOLDS[1] = 700;
            Parameters.LANDMARK_AGE_THRESHOLDS[2] = 2800;
            Parameters.LANDMARK_AGE_THRESHOLDS[3] = 4000;
            Parameters.FORGETTING_CURVE_EXPONENT[0] = -0.98;
            Parameters.FORGETTING_CURVE_EXPONENT[1] = -0.7;
            Parameters.FORGETTING_CURVE_EXPONENT[2] = -0.6;
            Parameters.FORGETTING_CURVE_EXPONENT[3] = -0.5;

            // Set mem parameters here.
            mem = new AgentMemory();
            mem.parameters.FORGETTING_CURVE_COEFFICIENT = 0.5;

            mem.parameters.REMEMBER_SEEN_ITEMS = true;
            mem.parameters.MAX_OBJECT_SCORE = 50;
            mem.parameters.DEFAULT_OBJECT_SCORE = 30;
            mem.parameters.MIN_OBJECT_SCORE = 5;
            mem.parameters.PERCENTAGE_TO_DERIVE_OBJECT = 0.6;

            // Initialize the lisp tree
            LispTree parser;
            parser = new LispTree(parameters.eventtree);

            RecallAccuracyPreHook preHook = new RecallAccuracyPreHook(mem);
            RecallAccuracyPostHook postHook = new RecallAccuracyPostHook(mem);

            // Initialize memory feeder
            MemoryFeeder feeder = new MemoryFeeder(mem, parser, parameters.descriptor, preHook, postHook);

            // Feed tree into memory
            try {
                parser.walkInForest(new TraceToEventVisitor(null, null, null, feeder));
            } catch (Exception e) {
                System.err.println("Error: " + e.getMessage());
            }

            return true;
        }
        return false;
    }

// ********************* Temporal schemas ******************************
    public class TemporalSchemasPreHook implements MemoryEventHooks {
        AgentMemory mem;
        DaysCounter days = new DaysCounter(true);
        int timelimit;

        public int wantedGoals = 0;

        public TemporalSchemasPreHook(AgentMemory _mem, int _timelimit) {
            mem = _mem;
            timelimit = _timelimit;
        }

        public boolean startNode(String goal, ArrayList<String> trace, ArrayList<String> aff) {
                if (goal.equalsIgnoreCase("SUNGOAL") || goal.equalsIgnoreCase("WEDGOAL") || goal.equalsIgnoreCase("TUEGOAL")) {
                    wantedGoals++;
                }
                return true;
        }
        public boolean closeNode(String goal, ArrayList<String> trace) { return true; }
        public boolean executeAction(String action, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean advanceTime(long _time) { return true; }
        public boolean endOfDay() {
            days.incDay();
            if (days.days == this.timelimit)
                return false;

            return true;
        }
    }

    public class TemporalSchemasPostHook implements MemoryEventHooks {
        AgentMemory mem;

        public TemporalSchemasPostHook (AgentMemory _mem) {
            mem = _mem;
        }

        public boolean startNode(String goal, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean closeNode(String goal, ArrayList<String> trace) { return true; }
        public boolean executeAction(String action, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean advanceTime(long _time) { return true; }
        public boolean endOfDay() { return true; }
    }

    public class PrecisionWatch {
        public HashMap<Integer, Double> map = new HashMap<Integer, Double>();
        public HashMap<Integer, Double> mapSchema = new HashMap<Integer, Double>();
        public int max, min;
        public HashMap<Integer, Double> map2 = new HashMap<Integer, Double>();
        public HashMap<Integer, Double> mapSchema2 = new HashMap<Integer, Double>();

        public PrecisionWatch() {
            max = 0;
            min = 0;
        }

        public void incOne(int key, double val, HashMap<Integer, Double> _map) {
            if (!_map.containsKey(key)) {
                _map.put(key, val);
            } else {
                _map.put(key, _map.get(key) + val);
            }
            if (key < min)
                min = key;
            if (key > max)
                max = key;
        }

        public void addEpisodeY(Episode e) {
            addEpisodeStd(e, map);
            addEpisodeSchema(e, mapSchema);
        }

        public void addEpisodeN(Episode e) {
            addEpisodeStd(e, map2);
            addEpisodeSchema(e, mapSchema2);
        }

        public void addEpisodeStd(Episode e, HashMap<Integer, Double> m) {
            int interval = e.getParentChronobag().begin - e.getParentChronobag().end + 1;
            for(int i=e.getParentChronobag().end; i <= e.getParentChronobag().begin; i++) {
                incOne(e.realAge - i, ((double)1 / (double)interval), m);
            }
        }
        public void addEpisodeSchema(Episode e, HashMap<Integer, Double> m) {
            // Verify the age speculations by temporal schema bag.
            Collection<SchemaEpisodeNode> nodes = e.root.getChildrenSchemaNodes();
            nodes.add(e.root.associatedNode.getAssociatedNode());
            // Now look at counters for the given nodes.
            int[] counts1 = new int[7]; // weeks
            int[] counts2 = new int[4]; // months

            // Calculate schemas properly.
            TemporalSchema s = mem.schemas.counters;
            if(e.getParentChronobag().getLevel() > 0) {
                for (int i=0; i<7; i++) {
                    Collection<SchemaCounter> counters = mem.schemas.schemaCounterSupersetSelector(1, i, nodes, new ArrayList<SlotContent>());
                    mem.schemas.schemaCounterSort(counters);
                    if (!counters.isEmpty()) {
                        counts1[i] = counters.iterator().next().getCount();
                    }
                }
            }
            if(e.getParentChronobag().getLevel() > 1) {
                for (int i=0; i<4; i++) {
                    Collection<SchemaCounter> counters = mem.schemas.schemaCounterSupersetSelector(2, i, nodes, new ArrayList<SlotContent>());
                    mem.schemas.schemaCounterSort(counters);
                    if (!counters.isEmpty()) {
                        counts2[i] = counters.iterator().next().getCount();
                    }
                }
            }
            int interval = e.getParentChronobag().begin - e.getParentChronobag().end + 1;
            // Calculate the actual probability distribution.

            double[] countsTot = new double[interval];
            for(int i=0; i<interval; i++) {
                // Initialize with basic probability.
                countsTot[i] = 1;
            }
            if(e.getParentChronobag().getLevel() > 0) {
                double tot = 0;
                for(int i=0; i<7; i++) {
                    tot += counts1[i % 7];
                }
                for(int i=0; i<interval; i++) {
                    countsTot[i] *= (double)counts1[i % 7] / tot;
                }
            }
            if(e.getParentChronobag().getLevel() > 1) {
                double tot = 0;
                for(int i=0; i<4; i++) {
                    tot += counts2[i % 4];
                }
                for(int i=0; i<interval; i++) {
                    countsTot[i] *= (double)counts2[i % 4] / tot;
                }
            }

            int j=0;
            for(int i=e.getParentChronobag().end; i <= e.getParentChronobag().begin; i++) {
                incOne(e.realAge - i, countsTot[j++], m);
            }
        }

        public void print() {
            System.out.println("Probability distribution of unevenly scheduled episodes.");
            System.out.println("- ;  Scheduled episodes ;; Unscheduled episodes ;;");
            System.out.println("Offset; w/o temporal schemas;  w/ temporal schemas; w/o temporal schemas; w/ temporal schemas");
            // Make sure the bottom exists to not skew graphs.
            for (int i=min; i <= max; i++) {
                double l=0, r=0, o=0, p=0;
                if (map.containsKey(i)) {
                    l = map.get(i);
                }
                if (mapSchema.containsKey(i)) {
                    r = mapSchema.get(i);
                }
                if (map2.containsKey(i)) {
                    o = map2.get(i);
                }
                if (mapSchema2.containsKey(i)) {
                    p = mapSchema2.get(i);
                }
                System.out.println(i + "; " + l + "; " + r + "; " + o + "; " + p + "; ");
            }
        }
    }

    boolean experiment3TemporalSchemas() {
        System.out.println("* Temporal schemas verify time precision of episode recall vs. focusing");
        System.out.println("* using higher level schemas.");
        if (parameters.eventtree != "") {
            System.err.println("Info: Using " + parameters.eventtree + " as input.");
            if (parameters.descriptor == "") {
                System.err.println("Error: descriptor for this domain does not exist or none specified.");
            }
            // Set static parameters here.
            Parameters.CHRONOBAG_INTERVAL_LENGHTS[0] = 1;
            Parameters.CHRONOBAG_INTERVAL_LENGHTS[1] = 7;
            Parameters.CHRONOBAG_INTERVAL_LENGHTS[2] = 28;
            Parameters.CHRONOBAG_INTERVAL_LENGHTS[3] = 336;
            Parameters.LANDMARK_AGE_THRESHOLDS[0] = 300;
            Parameters.LANDMARK_AGE_THRESHOLDS[1] = 700;
            Parameters.LANDMARK_AGE_THRESHOLDS[2] = 2800;
            Parameters.LANDMARK_AGE_THRESHOLDS[3] = 4000;
            Parameters.FORGETTING_CURVE_EXPONENT[0] = -0.8;
            Parameters.FORGETTING_CURVE_EXPONENT[1] = -0.5;
            Parameters.FORGETTING_CURVE_EXPONENT[2] = -0.5;
            Parameters.FORGETTING_CURVE_EXPONENT[3] = -0.5;

            // Set mem parameters here.
            mem = new AgentMemory();
            mem.parameters.FORGETTING_CURVE_COEFFICIENT = 0.5;
            mem.parameters.NO_EPISODE_MERGING = true;

            mem.parameters.REMEMBER_SEEN_ITEMS = true;
            mem.parameters.MAX_OBJECT_SCORE = 50;
            mem.parameters.DEFAULT_OBJECT_SCORE = 30;
            mem.parameters.MIN_OBJECT_SCORE = 5;
            mem.parameters.PERCENTAGE_TO_DERIVE_OBJECT = 0.6;

            // Initialize the lisp tree
            LispTree parser;
            parser = new LispTree(parameters.eventtree);

            TemporalSchemasPreHook preHook = new TemporalSchemasPreHook(mem, parameters.timeLimit);
            TemporalSchemasPostHook postHook = new TemporalSchemasPostHook(mem);

            // Initialize memory feeder
            MemoryFeeder feeder = new MemoryFeeder(mem, parser, parameters.descriptor, preHook, postHook);

            // Feed tree into memory
            try {
                parser.walkInForest(new TraceToEventVisitor(null, null, null, feeder));
            } catch (Exception e) {
                System.err.println("Error: " + e.getMessage());
            }
            System.out.println("Sunday/Wednesday/Tuesday episodes recognised: " + preHook.wantedGoals);

            PrecisionWatch Schemas = new PrecisionWatch();

            Collection<Episode> allEpisodes = this.mem.chrono.getEpisodeCollection();
            Collection<Episode> unwantedEpisodes = this.mem.chrono.getEpisodeCollection(); // TODO: These for some reason mess up the data.
            allEpisodes = this.mem.chrono.episodeRootnameSelector(allEpisodes, "SUNGOAL");
            System.out.println("Found sunday episodes: " + allEpisodes.size());

            Collection<Episode> xEpisodes = this.mem.chrono.getEpisodeCollection();
            xEpisodes = this.mem.chrono.episodeRootnameSelector(xEpisodes, "WEDGOAL");
            System.out.println("Found wednesday episodes: " + xEpisodes.size());
            unwantedEpisodes.addAll(xEpisodes);

            xEpisodes = this.mem.chrono.getEpisodeCollection();
            xEpisodes = this.mem.chrono.episodeRootnameSelector(xEpisodes, "TUEGOAL");
            System.out.println("Found tuesday episodes: " + xEpisodes.size());
            unwantedEpisodes.addAll(xEpisodes);
            // Now allEpisodes are all Sun/Wed/Tue scheduled episodes.

            System.out.println("Real ages of scheduled episodes:");
            for (Episode e : allEpisodes) {
                System.out.println(e.realAge + ":" + e.getParentChronobag().getLevel());
                Schemas.addEpisodeY(e);
            }

            // Let's also get all others, minus the ones above, for comparison.
            xEpisodes = this.mem.chrono.getEpisodeCollection();
            xEpisodes.removeAll(allEpisodes);
            //xEpisodes.removeAll(unwantedEpisodes);

            for (Episode e : xEpisodes) {
                Schemas.addEpisodeN(e);
            }

            Schemas.print();

            return true;
        }
        return false;
    }

// ********************* Total volume of memories ************************

    public class TotalVolumePreHook implements MemoryEventHooks {
        AgentMemory mem;

        public TotalVolumePreHook(AgentMemory _mem) {
            mem = _mem;
        }

        public boolean startNode(String goal, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean closeNode(String goal, ArrayList<String> trace) { return true; }
        public boolean executeAction(String action, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean advanceTime(long _time) { return true; }
        public boolean endOfDay() {
            return true;
        }
    }

    public class TotalVolumePostHook implements MemoryEventHooks {
        AgentMemory mem;
        DaysCounter days = new DaysCounter(true);
        int timelimit;

        public TotalVolumePostHook (AgentMemory _mem, int _timelimit) {
            mem = _mem;
            timelimit = _timelimit;
        }

        public boolean startNode(String goal, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean closeNode(String goal, ArrayList<String> trace) { return true; }
        public boolean executeAction(String action, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean advanceTime(long _time) { return true; }
        public boolean endOfDay() {
            days.incDay();
            // Print out totals by significant dates.
            if (((days.days) == 10) || ((days.days) == 100) || ((days.days) == 1000) || ((days.days) == 10000)) {
                VolumeCounter counter = new VolumeCounter();
                // Pick all episodes, and feed their parent chronobag to the counter.
                for (Episode e : mem.chrono.getEpisodeCollection()) {
                    counter.addChbag(e.getParentChronobag());
                }
                counter.print();
            }
            if (days.days > timelimit) {
                return false;
            }
            return true;
        }
    }

    public class VolumeCounter {
        public HashMap<Integer, Double> volumeMap = new HashMap<Integer, Double>();
        public int top = 0;

        public void incOne(int key, double val) {
            if (!volumeMap.containsKey(key)) {
                volumeMap.put(key, val);
            } else {
                volumeMap.put(key, volumeMap.get(key) + val);
            }
        }

        public void addChbag(Chronobag ch) {
            // Calculate the interval length N, and add 1/N to each
            double intlength = ch.begin - ch.end + 1;
            for (int i = ch.end; i <= ch.begin; i++) {
                //incOne(i, 1 / ((double)intlength));
                incOne(i, 1);
            }
            // Highest seen age.
            top = Math.max(top, ch.begin);
        }

        public void print() {
            System.out.println("Total volume by age (" + top + ")");
            // Make sure the bottom exists to not skew graphs.
            incOne(0,0);
            for (int i=0; i <= top; i++) {
                if (volumeMap.containsKey(i)) {
                    System.out.println(i + "; " + volumeMap.get(i) + ";");
                }
            }
        }
    }

    boolean experiment4TotalVolume() {
        System.out.println("* Total volume by time measurement.");
        System.out.println("* Explores the spread of memories across the past life.");
        if (parameters.eventtree != "") {
            System.err.println("Info: Using " + parameters.eventtree + " as input.");
            if (parameters.descriptor == "") {
                System.err.println("Error: descriptor for this domain does not exist or none specified.");
            }

            // Set static parameters here.
            Parameters.CHRONOBAG_INTERVAL_LENGHTS[0] = 1;
            Parameters.CHRONOBAG_INTERVAL_LENGHTS[1] = 7;
            Parameters.CHRONOBAG_INTERVAL_LENGHTS[2] = 28;
            Parameters.CHRONOBAG_INTERVAL_LENGHTS[3] = 336;
            Parameters.LANDMARK_AGE_THRESHOLDS[0] = 300;
            Parameters.LANDMARK_AGE_THRESHOLDS[1] = 700;
            Parameters.LANDMARK_AGE_THRESHOLDS[2] = 2800;
            Parameters.LANDMARK_AGE_THRESHOLDS[3] = 4000;
            Parameters.FORGETTING_CURVE_EXPONENT[0] = -1.2;
            Parameters.FORGETTING_CURVE_EXPONENT[1] = -0.7;
            Parameters.FORGETTING_CURVE_EXPONENT[2] = -0.5;
            Parameters.FORGETTING_CURVE_EXPONENT[3] = -0.4;

            // Set mem parameters here.
            mem = new AgentMemory();
            mem.parameters.FORGETTING_CURVE_COEFFICIENT = 0.5;

            mem.parameters.REMEMBER_SEEN_ITEMS = true;
            mem.parameters.MAX_OBJECT_SCORE = 50;
            mem.parameters.DEFAULT_OBJECT_SCORE = 30;
            mem.parameters.MIN_OBJECT_SCORE = 5;
            mem.parameters.PERCENTAGE_TO_DERIVE_OBJECT = 0.6;

            System.out.println("* Power function: " + mem.parameters.FORGETTING_CURVE_COEFFICIENT + " * x ^ " + "[" +
            Parameters.FORGETTING_CURVE_EXPONENT[0] + "," +
            Parameters.FORGETTING_CURVE_EXPONENT[1] + "," +
            Parameters.FORGETTING_CURVE_EXPONENT[2] + "," +
            Parameters.FORGETTING_CURVE_EXPONENT[3] + "]");

            // Initialize the lisp tree
            LispTree parser;
            parser = new LispTree(parameters.eventtree);

            TotalVolumePreHook preHook = new TotalVolumePreHook(mem);
            TotalVolumePostHook postHook = new TotalVolumePostHook(mem, parameters.timeLimit);

            // Initialize memory feeder
            MemoryFeeder feeder = new MemoryFeeder(mem, parser, parameters.descriptor, preHook, postHook);

            // Feed tree into memory
            try {
                parser.walkInForest(new TraceToEventVisitor(null, null, null, feeder));
            } catch (Exception e) {
                System.err.println("Error: " + e.getMessage());
            }

            return true;
        }
        return false;
    }

// ******************** Basic experiment *******************************

    boolean experiment999Basic() {
        System.out.println("* Basic functionality");
        if (parameters.eventtree != "") {
            System.err.println("Info: Using " + parameters.eventtree + " as input.");
            if (parameters.descriptor == "") {
                System.err.println("Error: descriptor for this domain does not exist or none specified.");
            }
            // Initialize the lisp tree
            LispTree parser;
            parser = new LispTree(parameters.eventtree);

            // Initialize memory feeder
            MemoryFeeder feeder = new MemoryFeeder(mem, parser, parameters.descriptor);

            // Feed tree into memory
            try {
                parser.walkInForest(new TraceToEventVisitor(null, null, null, feeder));
                return true;
            } catch (Exception e) {
                System.err.println("Error: " + e.getMessage());
            }
        }
        return false;
    }

// ******************** Longterm experiment *******************************

    public class TimeLimitHook implements MemoryEventHooks {
        public int limit;
        public DaysCounter days = new DaysCounter(true);

        public TimeLimitHook(int _limit) {
            limit = _limit;
        }

        public boolean startNode(String goal, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean closeNode(String goal, ArrayList<String> trace) { return true; }
        public boolean executeAction(String action, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean advanceTime(long _time) { return true; }
        public boolean endOfDay() {
            days.incDay();
            if (days.days > limit) {
                return false;
            }
            return true;
        }
    }

    public class LongTermDebugPostHook implements MemoryEventHooks {
        AgentMemory mem;
        int lastseen = 0;

        public LongTermDebugPostHook (AgentMemory _mem) {
            mem = _mem;
        }

        public boolean startNode(String goal, ArrayList<String> trace, ArrayList<String> aff) { return true; }
        public boolean closeNode(String goal, ArrayList<String> trace) { return true; }
        public boolean executeAction(String action, ArrayList<String> trace, ArrayList<String> aff) {
            return true;
        }
        public boolean advanceTime(long _time) { return true; }
        public boolean endOfDay() {
            return true;
        }
    }

    boolean experiment998VeryLongData() {
        System.out.println("testVeryLongData Start");
        AgentMemory.visualizeLive = false;
        AgentMemory mem = new AgentMemory();
        mem.parameters.HIGHLEVEL_SCHEMA_MIN_SIZE = 4;
        LongTermDebugPostHook posthook = new LongTermDebugPostHook(mem);
        TimeLimitHook hook = new TimeLimitHook(15);
        LispTree parser = new LispTree("testdata70y.lisp");
        MemoryFeeder feeder = new MemoryFeeder(mem, parser, "testdata.desc", hook, posthook);

        parser.walkInForest(new TraceToEventVisitor(null, null, null, feeder));

        System.out.println("testVeryLongData Success");
        return true;
    }
}
